<?php
/**
 * 大厅
 */
namespace app\socket;

use think\worker\Server;
use think\facade\Cache;
use Workerman\Protocols\Websocket;
use app\service\User;

Class Socket extends Server
{
    public $cid; // 客户端id
    public $describe;
    public $rpc; // 请求的方法

    public function __construct()
    {
        parent::__construct();
        $this->describe = null;
        $this->rpc = null;
    }

    // 客户端连接
    public function onConnect($conn)
    {
        // 设置连接类型
        $conn->websocketType = Websocket::BINARY_TYPE_ARRAYBUFFER;

        $this->cid = $conn->id;
        echo "客户端端连接服务器id: ".$conn->id."\n";
        $this->sendDescribe($conn);
    }

    // 接收客户端数据
    public function onMessage($conn, $data)
    {
        $conn->lastMessageTime = time();
        $this->handleMessage($conn, $data);
    }

    // 客户端关闭连接
    public function onClose($conn)
    {
        echo "Close connection to id: ".$this->cid."\n";
    }

    // 处理客户端数据
    public function handleMessage($conn, $message)
    {
        $data = $this->parseDataToArray($message);
        $type = $data['type'];
        // dump($this->cid.":".json_encode($data));

        switch ($type) {
            case 1: // 处理第一次连接描述
                $this->handleDescribe($conn, $data['data']);
                break;

            case 2: // 处理调用函数
                $this->runActionWithRawMessage($conn, $data['data']);
                break;

            case 3: // 处理一个回调函数
                $this->handleCb($conn, $data['data']);
                break;
        }
    }

    // 处理一个描述的信息
    public function handleDescribe($conn, $data)
    {
        $this->startCbTimeout($conn);
    }

    // 处理调用的函数
    public function runActionWithRawMessage($conn, $data)
    {
        $this->runAction($conn, $data['an'], $data['args'], $data['cbID']);
    }

    // 处理一个回调函数
    public function handleCb($conn, $data)
    {
        $cbID = $data['cbID'];
        $cbData = $data['cbData'];

        // dump(json_encode($data));
        $this->sendRawData($conn, $sendData);
    }

    // 运行服务 | actionName 方法名 args 参数 cbId: id
    public function runAction($conn, $actionName, $args, $cbID)
    {
        if (!array_key_exists($actionName, $this->enumClassList)) {
            $errorMsg = 'Service call function '.$actionName.' is not defined!';
            return $this->errorLog($conn, $errorMsg);
        }

        if (empty($cbID)) {
            $this->errorLog($conn, '客户端ID错误');
            return false;
        }
        $result = $this->$actionName($args, $conn);

        $this->sendCallbackData($conn, $result, $cbID);
    }

    // 发送一个描述
    public function sendDescribe($conn)
    {
        $names = $this->getDescribeList();
        $sendData = [
            'type' => 1,
            'data' => [
                'des' => $names
            ]
        ];
        $this->sendRawData($conn, $sendData);
    }

    // 发送回调数据
    public function sendCallbackData($conn, $rawData, $cbID)
    {
        $sendData = [
            'type' => 3,
            'data' => [
                'cbData' => $rawData,
                'cbID'   => $cbID
            ]
        ];
        $this->sendRawData($conn, $sendData);
    }

    // 主动推动消息
    public function sendPush($conn, $rawData)
    {
        $sendData = [
            'type' => 4,
            'data' => [
                'cbData' => $rawData
            ]
        ];
        $this->sendRawData($conn, $sendData);
    }

    // 发送数据
    public function sendRawData($conn, $data)
    {
        $data = msgpack_pack($data);
        $conn->send($data);
    }

    // 解析数据
    public function parseDataToArray($data)
    {
        return msgpack_unpack($data);
    }

    // 获得可以调用的函数列表
    public function getDescribeList()
    {
        if ($this->describe === null) {
            $this->describe = [];
        } else {
            return $this->describe;
        }

        $this->describe = $this->enumClassList;
        return $this->describe;
    }

    // 启动超时检测
    public function startCbTimeout($conn)
    {
        $this->worker->onWorkerStart = function($worker) {
            Timer::add(1, function()use($worker) {
                $time_now = time();
                foreach($worker->connections as $conn) {
                    // 有可能该connection还没收到过消息，则lastMessageTime设置为当前时间
                    if (empty($conn->lastMessageTime)) {
                        $conn->lastMessageTime = $time_now;
                        continue;
                    }
                    // 上次通讯时间间隔大于心跳间隔，则认为客户端已经下线，关闭连接
                    if ($time_now - $conn->lastMessageTime > 55) {
                        $conn->close();
                    }
                }
            });
        };
    }

    // 根据token获取id
    public function tokenGetId($token)
    {
        if (empty($token)) {
            return $this->wsFailed('登录失败');
        }

        $uinfo = solveJwt($token);
        if (empty($uinfo['id'])) {
            return $this->wsFailed('登录失败，数据解析错误');
        }

        return [
            'uid' => $uinfo['id']
        ];
    }

    // 处理结果
    public function wsResult($result)
    {
        $result['ok'] = true;
        return $result;
    }

    // 成功返回
    public function wsSuccess($data = [], $msg = '')
    {
        return [
            'ok'    => true,
            'code'  => 200,
            'msg'   => $msg,
            'datas' => json_encode($data)
        ];
    }

    // 失败返回
    public function wsFailed($msg = '')
    {
        return [
            'ok'    => true,
            'code'  => 0,
            'msg'   => $msg
        ];
    }

    // 错误返回
    public function wsError()
    {
        return [
            'ok'    => false
        ];
    }

    // 心跳连接
    public function heartBeat($args, $conn)
    {
        return [ 'ok' => true ];
    }

    // 打印日志
    public function errorLog($conn, $msg)
    {
        echo $msg."\n";
        $this->sendRawData($conn, $msg);
    }
}
